`include "cpu_def.vh"

module control(
  input         clk  ,
  input         rst  ,
  input  [31:0] instr,

  output de_read_rs,
  output de_read_rt,

  output [          12:0]  ex_alu_op       , // alu operation code
  output                   ex_sel_alu_src_0, // alu src0 is sa
                                             //* 0: rdata from rs
                                             //* 1: sa
  output [            1:0] ex_sel_alu_src_1, // alu src1 is imm or hl read data
                                             //* 00: rdata from rt
                                             //* 01: sign extended imm
                                             //* 10: zero extended imm
                                             //* 11: hl read data
  output [            1:0] ex_sel_rf_waddr , // reg file write addr select
                                             //* 00: rd
                                             //* 01: rt
                                             //* 10: 31
  output [            1:0] ex_sel_br_target, // branch/jump target select
                                             //* 00: pc + simm << 2                 (br_target )
                                             //* 01: {pc[31:28], instr_index, 2'b00}(jp_target )
                                             //* 10: read from rs                   (rf_raddr_0)
  output                   sel_hl_wdata_ex , // hl write data select in ex stage
                                             // * 0: read from rst
                                             // * 1: div result 
  output                   ex_read_mem     , // load instr
  output                   ex_write_mem    , // store instr
  output                   ex_branch       , // branch instr
  output [`NR_LD_OP - 1:0] ex_ld_op        , // one-hot load operation
  output [`NR_ST_OP - 1:0] ex_st_op        , // one-hot store operation
  output [`NR_BR_OP - 1:0] ex_br_op        , // one-hot branch operatoin
  output                   ex_mul          , // instr uses mul
  output                   ex_mul_sign     , // select mul result sign
  output                   ex_div          , // div or divu instr
  output                   ex_div_sign     , // select div or divu
  output                   ex_chk_ov       , // check overflow exception in ex stage
  
  output                   wb_write_rf     , // instr write reg file
  output [            2:0] wb_sel_rf_wdata , // reg file write data from mem
                                             //* 000: alu result
                                             //* 001: data sram rdata
                                             //* 010: pc
                                             //* 011: cp0 read data
                                             //* 100: mul result
  output [            1:0] wb_write_hl     , // instr write hi or lo
  output                   sel_hl_wdata_wb , // mul div instr result sel
                                             //* 0: mul
                                             //* 1: div or rs
  output [            1:0] wb_hl_raddr     , // read hi lo raddr
                                             //* 0: lo
                                             //* 1: hi   
  output                   wb_cp0_wen      , // cp0 write enable
  output                   wb_sys          , // syscall exception
  output                   wb_brk          , // break exception
  output                   wb_ri           , // reserved instruction
  output                   wb_eret         , // exception return instr
  output                   wb_tlbp         , // instr tlbp
  output                   wb_tlbr         , // instr tlbr
  output                   wb_tlbwi        , // instr tlbwi
  output                   wb_tlbwr          // instr tlbwr
);

  wire [5:0] opcode = instr[`INSTR_OPCODE];
  wire [4:0] rs     = instr[`INSTR_RS    ];
  wire [4:0] rt     = instr[`INSTR_RT    ];
  wire [4:0] sa     = instr[`INSTR_SA    ];
  wire [5:0] funct  = instr[`INSTR_FUNCT ];

  reg [31:0] nr_j;
  reg [31:0] nr_br;
  reg [31:0] nr_ld;
  reg [31:0] nr_st;

  `INSTR_SLL  ;
  `INSTR_SRL  ;
  `INSTR_SRA  ;
  `INSTR_SLLV ;
  `INSTR_SRLV ;
  `INSTR_SRAV ;
  `INSTR_JR   ;
  `INSTR_JALR ;

  `INSTR_SYSCALL;
  `INSTR_BREAK  ;

  `INSTR_MFHI ;
  `INSTR_MTHI ;
  `INSTR_MFLO ;
  `INSTR_MTLO ;

  `INSTR_MULT ;
  `INSTR_MULTU;
  `INSTR_DIV  ;
  `INSTR_DIVU ;

  `INSTR_ADD  ;
  `INSTR_ADDU ;
  `INSTR_SUB  ;
  `INSTR_SUBU ;

  `INSTR_AND  ;
  `INSTR_OR   ;
  `INSTR_XOR  ;
  `INSTR_NOR  ;
  `INSTR_SLT  ;
  `INSTR_SLTU ;

  `INSTR_BLTZ  ;
  `INSTR_BGEZ  ;
  `INSTR_BLTZAL;
  `INSTR_BGEZAL;

  `INSTR_J    ;
  `INSTR_JAL  ;

  `INSTR_BEQ  ;
  `INSTR_BNE  ;
  `INSTR_BLEZ ;
  `INSTR_BGTZ ;

  `INSTR_ADDI ;
  `INSTR_ADDIU;
  `INSTR_SLTI ;
  `INSTR_SLTIU;
  `INSTR_ANDI ;
  `INSTR_ORI  ;
  `INSTR_XORI ;
  `INSTR_LUI  ;

  `INSTR_ERET ;
  `INSTR_MFC0 ;
  `INSTR_MTC0 ;
  `INSTR_TLBP ;
  `INSTR_TLBR ;
  `INSTR_TLBWI;
  `INSTR_TLBWR;

  `INSTR_MUL;

  `INSTR_LB   ;
  `INSTR_LH   ;
  `INSTR_LWL  ;
  `INSTR_LW   ;
  `INSTR_LBU  ;
  `INSTR_LHU  ;
  `INSTR_LWR  ;

  `INSTR_SB   ;
  `INSTR_SH   ;
  `INSTR_SWL  ;
  `INSTR_SW   ;
  `INSTR_SWR  ;

  `INSTR_MATCH;

  always@(posedge clk) begin
    if (rst) begin
      nr_j <= 0;
    end else if (instr_j || instr_jal) begin
      nr_j <= nr_j + 1;
    end
  end

  always@(posedge clk) begin
    if (rst) begin
      nr_br <= 0;
    end else if (instr_beq || instr_bne || instr_bltz || instr_bgez || instr_blez || instr_bgtz || instr_bgezal || instr_bltzal) begin
      nr_br <= nr_br + 1;
    end
  end

  assign de_read_rs =
    instr_sllv  || 
    instr_srlv  ||
    instr_srav  ||
    instr_jr    ||
    instr_jalr  ||
    instr_mthi  ||
    instr_mtlo  ||
    instr_mult  ||
    instr_multu ||
    instr_div   ||
    instr_divu  ||
    instr_add   ||
    instr_addu  ||
    instr_sub   ||
    instr_subu  ||
    instr_and   ||
    instr_or    ||
    instr_xor   ||
    instr_nor   ||
    instr_slt   ||
    instr_sltu  ||
    instr_bltz  ||
    instr_bgez  ||
    instr_bltzal||
    instr_bgezal||
    instr_beq   ||
    instr_bne   ||
    instr_blez  ||
    instr_bgtz  ||
    instr_addi  ||
    instr_addiu ||
    instr_slti  ||
    instr_sltiu ||
    instr_andi  ||
    instr_ori   ||
    instr_xori  ||
    instr_mul   ||
    instr_lb    ||
    instr_lh    ||
    instr_lwl   ||
    instr_lw    ||
    instr_lbu   ||
    instr_lhu   ||
    instr_lwr   ||
    instr_sb    ||
    instr_sh    ||
    instr_swl   ||
    instr_sw    ||
    instr_swr   ||
    instr_match ;
  assign de_read_rt =
    instr_sll   ||
    instr_srl   ||
    instr_sra   ||
    instr_sllv  ||
    instr_srlv  ||
    instr_srav  ||
    instr_mult  ||
    instr_multu ||
    instr_div   ||
    instr_divu  ||
    instr_add   ||
    instr_addu  ||
    instr_sub   ||
    instr_subu  ||
    instr_and   ||
    instr_or    ||
    instr_xor   ||
    instr_nor   ||
    instr_slt   ||
    instr_sltu  ||
    instr_beq   ||
    instr_bne   ||
    instr_mul   ||
    instr_sb    ||
    instr_sh    ||
    instr_swl   ||
    instr_sw    ||
    instr_swr   ||
    instr_mtc0  ||
    instr_match ;
  assign ex_alu_op[`OP_ADD ] = 
    instr_mthi  ||
    instr_mtlo  ||
    instr_add   ||
    instr_addu  ||

    instr_addi  ||
    instr_addiu ||
    instr_mfhi  ||
    instr_mflo  ;
  assign ex_alu_op[`OP_SUB ] = 
    instr_sub  ||
    instr_subu ;
  assign ex_alu_op[`OP_AND ] = 
    instr_and  ||
    instr_andi  ;
  assign ex_alu_op[`OP_NOR ] = instr_nor;
  assign ex_alu_op[`OP_OR  ] = 
    instr_or  ||
    instr_ori ;
  assign ex_alu_op[`OP_XOR ] = 
    instr_xor  ||
    instr_xori ;
  assign ex_alu_op[`OP_SLT ] = 
    instr_slt  ||
    instr_slti ;
  assign ex_alu_op[`OP_SLTU] = 
    instr_sltu  ||
    instr_sltiu ;
  assign ex_alu_op[`OP_SLL ] = 
    instr_sll  ||
    instr_sllv ;
  assign ex_alu_op[`OP_SRA ] = 
    instr_sra  ||
    instr_srav ;
  assign ex_alu_op[`OP_SRL ] = 
    instr_srl  ||
    instr_srlv ;
  assign ex_alu_op[`OP_LUI ] = instr_lui;
  assign ex_alu_op[12] = instr_match;
  
  assign ex_sel_alu_src_0 = 
    instr_sll ||
    instr_srl ||
    instr_sra ;
  assign ex_sel_alu_src_1[0] = 
    instr_addi  ||
    instr_addiu ||
    instr_slti  ||
    instr_sltiu ||
    instr_lui   ||
    instr_mfhi  ||
    instr_mflo  ||
    instr_lb    ||
    instr_lh    ||
    instr_lwl   ||
    instr_lw    ||
    instr_lbu   ||
    instr_lhu   ||
    instr_lwr   ||
    instr_sb    ||
    instr_sh    ||
    instr_swl   ||
    instr_sw    ||
    instr_swr   ;
  assign ex_sel_alu_src_1[1] = 
    instr_andi ||
    instr_ori  ||
    instr_xori ||
    instr_mfhi ||
    instr_mflo ;
  assign ex_sel_rf_waddr[0] = 
    instr_addi  ||
    instr_addiu ||
    instr_slti  ||
    instr_sltiu ||
    instr_andi  ||
    instr_ori   ||
    instr_xori  ||
    instr_lui   ||
    instr_lb    ||
    instr_lh    ||
    instr_lwl   ||
    instr_lw    ||
    instr_lbu   ||
    instr_lhu   ||
    instr_lwr   ||
    instr_mfc0  ;
  assign ex_sel_rf_waddr[1] = 
    instr_bltzal ||
    instr_bgezal ||
    instr_jal    ||
    instr_jalr   ;
  assign ex_sel_br_target[0] = 
    instr_jal ||
    instr_j   ;
  assign ex_sel_br_target[1] = 
    instr_jr   ||
    instr_jalr ;
  assign sel_hl_wdata_ex = 
    instr_div  ||
    instr_divu ;

  assign ex_read_mem = 
    instr_lb  ||
    instr_lh  ||
    instr_lwl ||
    instr_lw  ||
    instr_lbu ||
    instr_lhu ||
    instr_lwr ;
  assign ex_write_mem = 
    instr_sb  ||
    instr_sh  ||
    instr_swl ||
    instr_sw  ||
    instr_swr ;
  assign ex_branch = 
    instr_beq    ||
    instr_bne    ||
    instr_bltz   ||
    instr_bgez   ||
    instr_blez   ||
    instr_bgtz   ||
    instr_bltzal ||
    instr_bgezal ||
    instr_j      ||
    instr_jal    ||
    instr_jr     ||
    instr_jalr   ;

  assign ex_ld_op[`OP_LB ] = instr_lb ;
  assign ex_ld_op[`OP_LH ] = instr_lh ;
  assign ex_ld_op[`OP_LWL] = instr_lwl;
  assign ex_ld_op[`OP_LW ] = instr_lw ;
  assign ex_ld_op[`OP_LBU] = instr_lbu;
  assign ex_ld_op[`OP_LHU] = instr_lhu;
  assign ex_ld_op[`OP_LWR] = instr_lwr;

  assign ex_st_op[`OP_SB ] = instr_sb ;
  assign ex_st_op[`OP_SH ] = instr_sh ;
  assign ex_st_op[`OP_SWL] = instr_swl;
  assign ex_st_op[`OP_SW ] = instr_sw ;
  assign ex_st_op[`OP_SWR] = instr_swr;

  assign ex_br_op[`OP_BEQ   ] = instr_beq   ;
  assign ex_br_op[`OP_BNE   ] = instr_bne   ;
  assign ex_br_op[`OP_BLTZ  ] = instr_bltz  ;
  assign ex_br_op[`OP_BGEZ  ] = instr_bgez  ;
  assign ex_br_op[`OP_BLEZ  ] = instr_blez  ;
  assign ex_br_op[`OP_BGTZ  ] = instr_bgtz  ;
  assign ex_br_op[`OP_BLTZAL] = instr_bltzal;
  assign ex_br_op[`OP_BGEZAL] = instr_bgezal;
  assign ex_br_op[`OP_J     ] = instr_j     ;
  assign ex_br_op[`OP_JAL   ] = instr_jal   ;
  assign ex_br_op[`OP_JR    ] = instr_jr    ;
  assign ex_br_op[`OP_JALR  ] = instr_jalr  ;

  assign ex_mul = 
    instr_mult  || 
    instr_multu ||
    instr_mul   ;
  assign ex_mul_sign = 
    instr_mult ||
    instr_mul  ;

  assign ex_div = 
    instr_div  ||
    instr_divu ;
  assign ex_div_sign = instr_div;
  assign ex_chk_ov =
    instr_add  ||
    instr_sub  ||
    instr_addi ;

  assign wb_write_rf =
    instr_sll   ||
    instr_srl   ||
    instr_sra   ||
    instr_sllv  ||
    instr_srlv  ||
    instr_srav  ||

    instr_mfhi  ||
    instr_mflo  ||

    instr_add   ||
    instr_addu  ||
    instr_sub   ||
    instr_subu  ||

    instr_and   ||
    instr_or    ||
    instr_xor   ||
    instr_nor   ||
    instr_slt   ||
    instr_sltu  ||

    instr_bltzal||
    instr_bgezal||

    instr_jal   ||
    instr_jalr  ||

    instr_addi  ||
    instr_addiu ||
    instr_slti  ||
    instr_sltiu ||
    instr_andi  ||
    instr_ori   ||
    instr_xori  ||
    instr_lui   ||

    instr_mfc0  ||

    instr_mul   ||

    instr_lb    ||
    instr_lh    ||
    instr_lwl   ||
    instr_lw    ||
    instr_lbu   ||
    instr_lhu   ||
    instr_lwr   ||
    
    instr_match ;

  assign wb_sel_rf_wdata[0] = 
    instr_lb  ||
    instr_lh  ||
    instr_lwl ||
    instr_lw  ||
    instr_lbu ||
    instr_lhu ||
    instr_lwr ||
    instr_mfc0;
  assign wb_sel_rf_wdata[1] = 
    instr_bltzal ||
    instr_bgezal ||
    instr_jal    ||
    instr_jalr   ||
    instr_mfc0   ;
  assign wb_sel_rf_wdata[2] =
    instr_mul;
  assign wb_write_hl[0] = 
    instr_mtlo  ||
    instr_mult  || 
    instr_multu ||
    instr_div   ||
    instr_divu  ;
  assign wb_write_hl[1] = 
    instr_mthi  ||
    instr_mult  ||
    instr_multu ||
    instr_div   ||
    instr_divu  ;
  assign sel_hl_wdata_wb = 
    instr_div  ||
    instr_divu ||
    instr_mthi ||
    instr_mtlo ;
  assign wb_hl_raddr[0] = instr_mflo;
  assign wb_hl_raddr[1] = instr_mfhi;
  assign wb_cp0_wen = instr_mtc0;

  assign wb_sys     = instr_syscall;
  assign wb_brk     = instr_break  ;
  assign wb_ri = !(
    instr_sll     ||
    instr_srl     ||
    instr_sra     ||
    instr_sllv    ||
    instr_srlv    ||
    instr_srav    ||
    instr_jr      ||
    instr_jalr    ||
    instr_mfhi    ||
    instr_mthi    ||
    instr_mflo    ||
    instr_mtlo    ||
    instr_mult    ||
    instr_multu   ||
    instr_div     ||
    instr_divu    ||
    instr_add     ||
    instr_addu    ||
    instr_sub     ||
    instr_subu    ||
    instr_and     ||
    instr_or      ||
    instr_xor     ||
    instr_nor     ||
    instr_slt     ||
    instr_sltu    ||
    instr_j       ||
    instr_jal     ||
    instr_beq     ||
    instr_bne     ||
    instr_blez    ||
    instr_bgtz    ||
    instr_bltz    ||
    instr_bgez    ||
    instr_bltzal  ||
    instr_bgezal  ||
    instr_addi    ||
    instr_addiu   ||
    instr_slti    ||
    instr_sltiu   ||
    instr_andi    ||
    instr_ori     ||
    instr_xori    ||
    instr_lui     ||
    instr_eret    ||
    instr_mfc0    ||
    instr_mtc0    ||
    instr_tlbp    ||
    instr_tlbr    ||
    instr_tlbwi   ||
    instr_tlbwr   ||
    instr_mul     ||
    instr_lb      ||
    instr_lh      ||
    instr_lwl     ||
    instr_lw      ||
    instr_lbu     ||
    instr_lhu     ||
    instr_lwr     ||
    instr_sb      ||
    instr_sh      ||
    instr_swl     ||
    instr_sw      ||
    instr_swr     ||
    instr_syscall ||
    instr_break   ||
    instr_match
  );
  assign wb_eret  = instr_eret;
  assign wb_tlbp  = instr_tlbp;
  assign wb_tlbr  = instr_tlbr;
  assign wb_tlbwi = instr_tlbwi;
  assign wb_tlbwr = instr_tlbwr;

endmodule